package router

import (
	"fmt"
	"strings"

	"github.com/valyala/fasthttp/pprofhandler"

	"gitee.com/shibingli/fastweb/commons"
	"gitee.com/shibingli/fastweb/log"
	fRouter "github.com/fasthttp/router"
	"github.com/valyala/fasthttp"
	"go.uber.org/zap"
)

type Router struct {
	disableFaviconIcon bool
	serverInfo         string
	router             *fRouter.Router
	groupMap           map[string]*fRouter.Group
}

func NewRouter() *Router {
	r := fRouter.New()
	r.RedirectFixedPath = true

	return &Router{
		router:   r,
		groupMap: make(map[string]*fRouter.Group),
	}
}

func (r *Router) AddGroup(path string) {
	path = strings.TrimSpace(path)

	if 0 == len(path) {
		commons.Logger.Error("Router Error.", zap.String("Msg", "Unavailable request path"))
		return
	}

	if _, ok := r.groupMap[path]; !ok {
		r.groupMap[path] = r.router.Group(path)
	}
}

func (r *Router) AddRouter(method, path string, handler fasthttp.RequestHandler) {
	method = strings.TrimSpace(method)
	path = strings.TrimSpace(path)

	if 0 == len(method) {
		commons.Logger.Warn("Router Error.", zap.String("Msg", "Unavailable request method"))
		return
	}

	if 0 == len(path) {
		commons.Logger.Warn("Router Error.", zap.String("Msg", "Unavailable request path"))
		return
	}

	method = strings.ToUpper(method)

	r.router.Handle(method, path, handler)
}

func (r *Router) Handler(ctx *fasthttp.RequestCtx) {
	r.router.Handler(ctx)
}

func (r *Router) Get(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodGet, path, handler)
}

func (r *Router) Delete(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodDelete, path, handler)
}

func (r *Router) Put(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodPut, path, handler)
}

func (r *Router) Head(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodHead, path, handler)
}

func (r *Router) Options(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodOptions, path, handler)
}

func (r *Router) Patch(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodPatch, path, handler)
}

func (r *Router) Connect(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodConnect, path, handler)
}

func (r *Router) Trace(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.Handle(fasthttp.MethodTrace, path, handler)
}

func (r *Router) Any(path string, handler fasthttp.RequestHandler) {
	path = strings.TrimSpace(path)
	r.router.ANY(path, handler)
}

func (r *Router) StaticDir(path, rootPath string) {
	fs := &fasthttp.FS{
		Root:            strings.TrimSpace(rootPath),
		Compress:        true,
		AcceptByteRange: true,
		PathNotFound: func(ctx *fasthttp.RequestCtx) {
			ctx.NotFound()
		},
	}
	r.router.ServeFilesCustom(path, fs)
}

func (r *Router) DisableFaviconIco() {
	r.disableFaviconIcon = true
}

func (r *Router) SetServer(server string, version ...int) {
	server = strings.TrimSpace(server)

	if 0 == len(server) {
		server = commons.ServerName
	}

	if 0 == len(version) {
		r.serverInfo = server
	} else {
		r.serverInfo = fmt.Sprintf("%s/v%d", server, version[0])
	}
}

func (r *Router) SetLogger(logger *log.Logger) {
	commons.SetLogger(logger)
}

func (r *Router) ListenAndServe(addr ...string) error {
	address := fmt.Sprintf("%s:%s", commons.Env.ListenerIP, commons.Env.Port)

	if 0 != len(addr) {
		address = addr[0]
	}

	handler := fasthttp.CompressHandler(r.Handler)

	if r.disableFaviconIcon {
		r.Get("/favicon.ico", func(ctx *fasthttp.RequestCtx) {
			ctx.NotFound()
		})
	}

	if commons.Env.PProfEnable {
		if commons.Env.PProfEnable {
			go func() {
				if err := fasthttp.ListenAndServe(":7777", pprofhandler.PprofHandler); nil != err {
					commons.Logger.Fatal("Start pprof server error", zap.String("Error", err.Error()))
				}
			}()
		}
	}

	commons.Logger.Info("SrvInfo",
		zap.String("addr", address),
		zap.Bool("dev_mode", commons.Env.DevMode),
	)

	srv := fasthttp.Server{
		Name:               fmt.Sprintf("%s/v%s", commons.ServerName, commons.ServerVersion),
		Concurrency:        commons.Env.Concurrency,
		MaxConnsPerIP:      commons.Env.MaxConnsPerIP,
		MaxRequestsPerConn: commons.Env.MaxRequestsPerConn,
		Logger:             commons.Logger,
		Handler:            handler,
	}

	return srv.ListenAndServe(address)
}
